#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include "opt.h"

#if defined(__APPLE__) && defined(__MACH__)

# ifndef __unix__
#  define __unix__	1
# endif	/* unix */

# ifndef __bsd__
#  define __bsd__	1
# endif	/* bsd */

#endif	/* apple */

#ifdef __bsd__
#include <sys/sysctl.h>
#endif

enum {
	OPT_UNKNOWN,

	OPT_SIZE,
	OPT_AREA,
	OPT_SAMPLES,
	OPT_THREADS,
	OPT_BLOCKSIZE,
	OPT_FBSHM,
	OPT_UPDPID,
	OPT_QUIET,
	OPT_VERBOSE,
	OPT_HELP
};

static struct {
	int opt;
	char sopt;
	const char *lopt;
	const char *desc;
} options[] = {
	{OPT_SIZE,		's', "size",		"output image size: wxh"},
	{OPT_AREA,		'a', "area",		"image area to render (WxH+X+Y)"},
	{OPT_SAMPLES,	'r', "rays",		"rays per pixel: rays[-maxrays]"},
	{OPT_THREADS,	't', "threads",		"number of worker threads to spawn"},
	{OPT_BLOCKSIZE,	'b', "blocksz",		"rendering block dimensions"},
	{OPT_FBSHM,		'm', "fbshm",		"a POSIX shared memory path to use as framebuffer"},
	{OPT_UPDPID,	'u', "updpid",		"send USR1 for progress updates to some process"},
	{OPT_QUIET,		'q', "quiet",		"don't output too much info (stderr)"},
	{OPT_VERBOSE,	'v', "verbose",		"output a whole lot of info (stderr)"},
	{OPT_HELP,		'h', "help",		"print usage information and exit"},
	{0, 0, 0, 0}
};

static void default_opt(void);
static void print_opt(void);
static int get_opt(const char *arg);
static int count_processors(void);

int parse_opt(int argc, char **argv)
{
	int i, j;

	default_opt();

	for(i=1; i<argc; i++) {
		int opt_num = get_opt(argv[i]);

		switch(opt_num) {
		case OPT_SIZE:
			if(sscanf(argv[++i], "%dx%d", &opt.width, &opt.height) != 2) {
				fprintf(stderr, "%s must be followed by: WxH\n", argv[i - 1]);
				return -1;
			}
			break;

		case OPT_AREA:
			if(sscanf(argv[++i], "%dx%d+%d+%d", &opt.xsz, &opt.ysz, &opt.xmin, &opt.ymin) != 4) {
				fprintf(stderr, "invalid area specification, %s must be followed by: WxH+X+Y\n", argv[i - 1]);
				return -1;
			}
			break;

		case OPT_SAMPLES:
			{
				int n = sscanf(argv[++i], "%d-%d", &opt.min_samples, &opt.max_samples);
				if(n < 1) {
					fprintf(stderr, "%s must be followed by: rays[-max]\n", argv[i - 1]);
					return -1;
				} else if(n == 1) {
					opt.max_samples = opt.min_samples;
				}
			}
			break;

		case OPT_THREADS:
			if(!isdigit(argv[++i][0])) {
				fprintf(stderr, "%s must be followed by the number of worker threads\n", argv[i - 1]);
				return -1;
			}
			opt.threads = atoi(argv[i]);
			break;

		case OPT_BLOCKSIZE:
			if(!isdigit(argv[++i][0])) {
				fprintf(stderr, "%s must be followed by the block size\n", argv[i - 1]);
				return -1;
			}
			opt.blk_sz = atoi(argv[i]);
			break;

		case OPT_FBSHM:
			if(argv[++i][0] != '/') {
				fprintf(stderr, "a POSIX shm path must begin with a slash\n");
				return -1;
			}
			opt.shm = argv[i];
			break;

		case OPT_QUIET:
			opt.verb = 0;
			break;

		case OPT_VERBOSE:
			opt.verb = 2;
			break;

		case OPT_HELP:
			printf("rend options:\n");
			for(j=0; options[j].opt; j++) {
				printf("  -%c, -%-10s %s\n", options[j].sopt, options[j].lopt, options[j].desc);
			}
			exit(0);

		default:
			fprintf(stderr, "unknown option: %s. try -h for usage info.\n", argv[i]);
			return -1;
		}
	}

	if(opt.xsz == -1 || opt.ysz == -1) {
		opt.xsz = opt.width;
		opt.ysz = opt.height;
	}

	if(!opt.shm) {
		static char shmname[64];
		sprintf(shmname, "/rendy.%d.shm", (int)getpid());
		opt.shm = shmname;
	}

	if(!opt.threads) {
		if((opt.threads = count_processors()) <= 0) {
			opt.threads = 1;
		}
	}

	if(opt.verb == 2) {
		print_opt();
	}

	return 0;
}


static void default_opt(void)
{
	opt.width = 640;
	opt.height = 480;
	opt.xmin = opt.ymin = 0;
	opt.xsz = opt.ysz = -1;
	opt.min_samples = 1;
	opt.max_samples = 1;
	opt.threads = 0;
	opt.blk_sz = 64;
	opt.verb = 1;
	opt.shm = 0;
}

static void print_opt(void)
{
	fprintf(stderr, "render options\n--------------\n");
	fprintf(stderr, "     image size: %dx%d\n", opt.width, opt.height);
	fprintf(stderr, "    render area: %dx%d+%d+%d\n", opt.xsz, opt.ysz, opt.xmin, opt.ymin);
	fprintf(stderr, "        samples: %d-%d\n", opt.min_samples, opt.max_samples);
	fprintf(stderr, "        threads: %d\n", opt.threads);
	fprintf(stderr, "     block size: %d\n", opt.blk_sz);
	fprintf(stderr, "framebuffer shm: %s\n", opt.shm);
	fputc('\n', stderr);
}

static int get_opt(const char *arg)
{
	int i;

	if(arg[0] != '-') {
		return OPT_UNKNOWN;
	}

	for(i=0; options[i].opt; i++) {
		if(arg[2] == 0) {
			if(arg[1] == options[i].sopt) {
				return options[i].opt;
			}
		} else {
			if(strcmp(arg + 1, options[i].lopt) == 0) {
				return options[i].opt;
			}
		}
	}
	return OPT_UNKNOWN;
}

static int count_processors(void)
{

#if defined(unix) || defined(__unix__)
# if defined(__bsd__)
	int num, mib[] = {CTL_HW, HW_NCPU};
	size_t len = sizeof num;

	sysctl(mib, 2, &num, &len, 0, 0);
	return num;

# elif defined(__sgi)
	return sysconf(_SC_NPROC_ONLN);
# else
	return sysconf(_SC_NPROCESSORS_ONLN);
# endif	/* bsd/sgi/other */

#elif defined(WIN32) || defined(__WIN32__)
	SYSTEM_INFO info;
	GetSystemInfo(&info);
	return info.dwNumberOfProcessors;
#endif
}
